﻿using System;
using System.IO;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Serialization;


namespace Press.MVC.Web.ActionFilters
{
    /// <summary>
    /// Sample Action Filter showing how to modify the ActionResult in a helpful way.
    /// This particular filter will normally not do anything at all,
    /// but if you ask it to (by, say, attaching the "renderMode" querystring to a request)
    /// it will replace the current ActionResult with the appropriate serialized data.
    /// </summary>
    public class SerializableViewDataAttribute : ActionFilterAttribute
    {
        private enum SerializationMethod
        {
            None,
            Json,
            Xml,
            Partial
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            // Try to figure out what serialization method we're dealing with (if any)
            var method = getSerializationMethod(filterContext);

            // If this isn't a serializable request, just return
            if (method == SerializationMethod.None)
                return;

            // Decide which method to use
            switch (method)
            {
                case (SerializationMethod.Xml):
                    filterContext.Result = getXmlResult(filterContext.GetViewData());
                    break;
                case (SerializationMethod.Json):
                    filterContext.Result = getJsonResult(filterContext.GetViewData());
                    break;
                case (SerializationMethod.Partial):
                    // If this is supposed to be a partial request, just change the view name
                    var viewResult = ((ViewResult)filterContext.Result);
                    viewResult.ViewName = "Partial" + viewResult.ViewName;
                    break;
            }
        }

        private static SerializationMethod getSerializationMethod(RequestContext filterContext)
        {
            // Get the (lowercase) render mode from the querystring.
            // NOTE: This is what you would modify if you wanted to use other
            //       parts of the environment - such as Route Data, for instance -
            //       to glean the render mode
            var querystring = filterContext.HttpContext.Request.QueryString["renderMode"] ?? string.Empty;

            var method = SerializationMethod.None;
            try
            {
                if (!string.IsNullOrEmpty(querystring))
                    method = (SerializationMethod)Enum.Parse(typeof(SerializationMethod), querystring, true);
            }
            catch { /* Ignore any exceptions */ }

            return method;
        }

        private static ActionResult getJsonResult(object viewData)
        {
            // Since there's a JsonResult Action, 
            // all we have to do is pass in the data
            return new JsonResult
            {
                // Overridding the ContentType for easier demo-ing.
                // This doesn't need to be set for real apps.
                ContentType = "text",
                Data = viewData
            };
        }

        private static ActionResult getXmlResult(object viewData)
        {
            // There's no "XmlResult" so we have to serialize it ourselves
            var sb = new StringBuilder();
            using (var writer = new StringWriter(sb))
            {
                new XmlSerializer(viewData.GetType()).Serialize(writer, viewData);
            }

            // ...then pass it into a ContentResult
            return new ContentResult
            {
                ContentType = "text/xml",
                Content = sb.ToString()
            };
        }

    }


    public static class FilterExtensions
    {
        /// <summary>
        /// This is just a helper method to get the ViewData from 
        /// the ControllerContext without having to drill down 2 levels
        /// </summary>
        /// <param name="context"></param>
        /// <returns>The context's ViewData</returns>
        public static object GetViewData(this ControllerContext context)
        {
            // Cast the controller to the abstract Controller type
            var controller = context.Controller as Controller;

            // If we don't have a controller, bail out now
            if (controller == null)
                return null;

            // Get the view data from the controller.
            var viewData = controller.ViewData;

            // If it is strongly-typed (i.e. has a Model) 
            //   return only the model.
            // Otherwise, return the whole thing.
            return viewData != null && viewData.Model != null ? viewData.Model : viewData;
        }
    }

}
